/**
  ******************************************************************************
  * @file    modbus_host.c
  * @author  ashuai0110
  * @version V2.4
  * @date    2024-09-10
  * @brief   modbus主机程序(RTU ASCII)
  *
  ******************************************************************************
  * @attention
  *
  * 版权声明:内容为编者(ashuai0110)原创,使用请注明出处,当然,你也可以不这样做^_^
  * 出处链接:https://gitee.com/ashuai0110/mcu_reuse_development_module.git
  *
  * Change Logs:
  * Date          Author        Notes
  * 2023-10-27    ashuai0110    完成基本内容
  * 2024-01-02    ashuai0110    修改从组件计时节拍接口获取时基
  * 2024-07-24    ashuai0110    修改帧间隔时间为用户自定义参数
  * 2024-08-31    ashuai0110    修改超时判断逻辑,删除一些assert
  * 2024-09-09    ashuai0110    修改数组为对象形式
  *
  ******************************************************************************
  */

/* 包含头文件-----------------------------------------------------------------*/
#include "modbus_host.h"

/**
 * @addtogroup modbus
 * @{
 */

/**
 * @defgroup modbus_host modbus_host
 * @{
 */

/* 私有宏定义-----------------------------------------------------------------*/

/* 私有类型定义---------------------------------------------------------------*/

/* 私有变量-------------------------------------------------------------------*/

/* 全局变量-------------------------------------------------------------------*/

/* 私有函数原型---------------------------------------------------------------*/
/**
 * @defgroup modbus_host_local_functions modbus host local functions
 * @{
 */

/**
  * @brief  从机应答处理
  *
  * @param  _mbh : modbus主机管理实例
  *
  * @retval None
  */
static void mb_host_rspProcess(mb_host_t *_mbh)
{
    switch(_mbh->dataBuf[MB_CODE_OFF])
    {
        case 0x01: mb_host_rsp_01h(_mbh, _mbh->regTmp, _mbh->numTmp, &_mbh->dataBuf[MB_RSP_DATA_OFF]); break; /* 读线圈应答处理 */
        case 0x02: mb_host_rsp_02h(_mbh, _mbh->regTmp, _mbh->numTmp, &_mbh->dataBuf[MB_RSP_DATA_OFF]); break; /* 读离散量输入应答处理 */
        case 0x03: mb_host_rsp_03h(_mbh, _mbh->regTmp, _mbh->numTmp, &_mbh->dataBuf[MB_RSP_DATA_OFF]); break; /* 读保持寄存器应答处理 */
        case 0x04: mb_host_rsp_04h(_mbh, _mbh->regTmp, _mbh->numTmp, &_mbh->dataBuf[MB_RSP_DATA_OFF]); break; /* 读输入寄存器应答处理 */
        case 0x05: 
        case 0x06: 
        case 0x0F: 
        case 0x10: mb_host_rsp_05_10h(_mbh); break; /* 写命令应答处理 */
        /*
        用户在这里写其他功能码的应答处理
        */
        default: if(_mbh->dataBuf[MB_CODE_OFF] & 0x80) { mb_host_rsp_err(_mbh, &_mbh->dataBuf[MB_CODE_OFF]); } break; /* 不支持的功能码或错误返回 */
    }
}

/**
  * @brief  追加校验后发送
  *
  * @note   modbus的地址和数据项采用大端序,CRC16校验采用小端序
  *
  * @param  _mbh : modbus主机管理实例
  *
  * @retval None
  */
static void mb_host_send_with_check(mb_host_t *_mbh)
{
    uint16_t checkVal;
    
    /* ASCII协议 */
    if(MB_ASCII == _mbh->protocol)
    {
        /* 追加LRC校验结果 */
        checkVal = mb_check_lrc(_mbh->dataBuf, _mbh->dataLen);
        _mbh->dataBuf[_mbh->dataLen++] = checkVal;
        /* HEX转ASCII */
        mb_hex_to_ascii(&_mbh->dataBuf[0], _mbh->dataLen, &_mbh->dataBuf[0]);
        _mbh->dataLen = (_mbh->dataLen << 1) + 3;
        /* 添加帧头帧尾标志符 */
        _mbh->dataBuf[0] = 0x3A;
        _mbh->dataBuf[_mbh->dataLen - 2] = 0x0D;
        _mbh->dataBuf[_mbh->dataLen - 1] = 0x0A;
    } /* RTU协议 */
    else
    {
        /* 追加CRC校验结果 */
        checkVal = mb_check_crc16(_mbh->dataBuf, _mbh->dataLen);
        _mbh->dataBuf[_mbh->dataLen++] = checkVal;
        _mbh->dataBuf[_mbh->dataLen++] = checkVal >> 8;
    }
#if _MBH_DEBUG
    MW_PRINT_FUNC("modbus host[%d] send: ", ch);
    for(uint16_t i = 0; i < _mbh->dataLen; i++)
    {
        MW_PRINT_FUNC("%02X ", _mbh->dataBuf[i]);
    }
    MW_PRINT_FUNC("\r\n");
#endif
    _mbh->state = MB_TX_BUSY; /* 切换状态 */
    _mbh->txEnFn(1); /* 开启发送 */
}

/**
  * @brief  功能码0x01 0x02 0x03 0x04命令
  *
  * @note   从机地址 | 功能码 | 起始地址 | 数量 | CRC16
  *            1     |   1    |    2     |  2   |   2   (占用字节)
  *
  * @param  _mbh      : modbus主机管理实例
  *
  * @param  slaveAddr : 从机地址
  *
  * @param  funCode   : 功能码
  *
  * @param  reg       : 起始地址
  *
  * @param  num       : 数量
  *
  * @retval None
  */
static void mb_host_cmd_01_04h(mb_host_t *_mbh, uint8_t slaveAddr, uint8_t funCode, uint16_t reg, uint16_t num)
{
    /* 根据协议对应填值 */
    _mbh->dataLen = 0;
    _mbh->dataBuf[_mbh->dataLen++] = slaveAddr;
    _mbh->dataBuf[_mbh->dataLen++] = funCode;
    _mbh->dataBuf[_mbh->dataLen++] = reg >> 8;
    _mbh->dataBuf[_mbh->dataLen++] = reg;
    _mbh->dataBuf[_mbh->dataLen++] = num >> 8;
    _mbh->dataBuf[_mbh->dataLen++] = num;
    
    mb_host_send_with_check(_mbh);
}

/**
  * @brief  写单个线圈命令(0x05)
  *
  * @note   从机地址 | 功能码 | 起始地址 | 写入值 | CRC16
  *            1     |   1    |    2     |   2    |   2   (占用字节)
  *
  * @param  _mbh      : modbus主机管理实例
  *
  * @param  slaveAddr : 从机地址
  *
  * @param  reg       : 起始地址
  *
  * @param  extend    : 扩展数据(一个线圈数据 0:OFF 1:ON)
  *
  * @retval None
  */
static void mb_host_cmd_05h(mb_host_t *_mbh, uint8_t slaveAddr, uint16_t reg, uint8_t extend)
{
    /* 根据协议对应填值 */
    _mbh->dataLen = 0;
    _mbh->dataBuf[_mbh->dataLen++] = slaveAddr;
    _mbh->dataBuf[_mbh->dataLen++] = 0x05;
    _mbh->dataBuf[_mbh->dataLen++] = reg >> 8;
    _mbh->dataBuf[_mbh->dataLen++] = reg;
    _mbh->dataBuf[_mbh->dataLen++] = extend ? 0xFF : 0x00; /* 写入值:0xFF00=ON 0x0000=OFF */
    _mbh->dataBuf[_mbh->dataLen++] = 0x00;
    
    mb_host_send_with_check(_mbh);
}

/**
  * @brief  写单个保持寄存器命令(0x06)
  *
  * @note   从机地址 | 功能码 | 起始地址 | 写入值 | CRC16
  *            1     |   1    |    2     |   2    |   2   (占用字节)
  *
  * @param  _mbh      : modbus主机管理实例
  *
  * @param  slaveAddr : 从机地址
  *
  * @param  reg       : 起始地址
  *
  * @param  extend    : 扩展数据(一个寄存器数据 16bit)
  *
  * @retval None
  */
static void mb_host_cmd_06h(mb_host_t *_mbh, uint8_t slaveAddr, uint16_t reg, uint16_t extend)
{
    /* 根据协议对应填值 */
    _mbh->dataLen = 0;
    _mbh->dataBuf[_mbh->dataLen++] = slaveAddr;
    _mbh->dataBuf[_mbh->dataLen++] = 0x06;
    _mbh->dataBuf[_mbh->dataLen++] = reg >> 8;
    _mbh->dataBuf[_mbh->dataLen++] = reg;
    _mbh->dataBuf[_mbh->dataLen++] = extend >> 8; 
    _mbh->dataBuf[_mbh->dataLen++] = extend;
    
    mb_host_send_with_check(_mbh);
}

/**
  * @brief  写多个线圈命令(0x0f)
  *
  * @note   从机地址 | 功能码 | 起始地址 | 数量n | 字节数2n | 写入值 | CRC16
  *            1     |   1    |    2     |   2   |    1     |   2n   |   2   (占用字节)
  *
  * @param  _mbh      : modbus主机管理实例
  *
  * @param  slaveAddr : 从机地址
  *
  * @param  reg       : 起始地址
  *
  * @param  num       : 数量
  *
  * @param  extend    : 扩展数据(多个线圈数据 1字节型指针)
  *
  * @retval None
  */
static void mb_host_cmd_0fh(mb_host_t *_mbh, uint8_t slaveAddr, uint16_t reg, uint16_t num, uint8_t *extend)
{
    uint16_t bytes = (num + 7) >> 3;
    
    /* 根据协议对应填值 */
    _mbh->dataLen = 0;
    _mbh->dataBuf[_mbh->dataLen++] = slaveAddr;
    _mbh->dataBuf[_mbh->dataLen++] = 0x0F;
    _mbh->dataBuf[_mbh->dataLen++] = reg >> 8;
    _mbh->dataBuf[_mbh->dataLen++] = reg;
    _mbh->dataBuf[_mbh->dataLen++] = num >> 8;
    _mbh->dataBuf[_mbh->dataLen++] = num;
    _mbh->dataBuf[_mbh->dataLen++] = bytes;
    num = num % 8;
    if(num) { extend[bytes - 1] = extend[bytes - 1] & ((1u << num) - 1); } /* 未使用bit填0 */
    memcpy(&_mbh->dataBuf[_mbh->dataLen], extend, bytes);
    _mbh->dataLen += bytes;

    mb_host_send_with_check(_mbh);
}

/**
  * @brief  写多个寄存器命令(0x10)
  *
  * @note   从机地址 | 功能码 | 起始地址 | 数量n | 字节数2n | 写入值 | CRC16
  *            1     |    1   |    2     |   2   |    1     |   2n   |   2   (占用字节)
  *
  * @param  _mbh      : modbus主机管理实例
  *
  * @param  slaveAddr : 从机地址
  *
  * @param  reg       : 起始地址
  *
  * @param  num       : 数量
  *
  * @param  extend    : 扩展数据(多个寄存器数据 2字节型指针)
  *
  * @retval None
  */
static void mb_host_cmd_10h(mb_host_t *_mbh, uint8_t slaveAddr, uint16_t reg, uint16_t num, uint16_t *extend)
{
    uint16_t bytes = num << 1;
    
    /* 根据协议对应填值 */
    _mbh->dataLen = 0;
    _mbh->dataBuf[_mbh->dataLen++] = slaveAddr;
    _mbh->dataBuf[_mbh->dataLen++] = 0x10;
    _mbh->dataBuf[_mbh->dataLen++] = reg >> 8;
    _mbh->dataBuf[_mbh->dataLen++] = reg;
    _mbh->dataBuf[_mbh->dataLen++] = num >> 8;
    _mbh->dataBuf[_mbh->dataLen++] = num;
    _mbh->dataBuf[_mbh->dataLen++] = bytes;
    for(uint16_t cnt = 0; cnt < num; cnt++)
    {
        _mbh->dataBuf[_mbh->dataLen++] = extend[cnt] >> 8;
        _mbh->dataBuf[_mbh->dataLen++] = extend[cnt];
    }

    mb_host_send_with_check(_mbh);
}

/**
 * @}
 */

/**
 * @defgroup modbus_host_global_functions modbus host global functions
 * @{
 */

/**
  * @brief  modbus主机初始化
  *
  * @param  _mbh       : modbus主机管理实例
  *
  * @param  protocol   : 协议类型 @ref modbus_common_protocol
  *   @arg  MB_RTU     : RTU协议
  *   @arg  MB_ASCII   : ASCII协议
  *
  * @param  pBuf       : 接收数据缓存区
  *
  * @param  bufLen     : 接收数据缓存区长度
  *
  * @param  txEnFn     : 主机发送前(串口)准备函数
  *
  * @param  frameIntv  : 帧间隔超时时间ms,填0则帧间隔超时时间为0
  *
  * @param  rspTimeout : 应答超时时间ms
  *
  * @retval None
  */
void mb_host_init(mb_host_t *_mbh, uint8_t protocol, void *pBuf, uint16_t bufLen, modbus_tx_en_t txEnFn, uint32_t frameIntv, uint32_t rspTimeout)
{
    MW_ASSERT_PARAM(IS_VALID_POINTER(_mbh));
    MW_ASSERT_PARAM(IS_VALID_MB_PROTOCOL(protocol));
    MW_ASSERT_PARAM(IS_VALID_POINTER(pBuf));
    MW_ASSERT_PARAM(IS_VALID_POINTER(txEnFn));
    
    /* 初始化各参数 */
    memset(_mbh, 0, sizeof(mb_host_t));
    _mbh->dataBuf = (uint8_t *)pBuf;
    _mbh->dataBufLen = bufLen;
    _mbh->protocol = protocol;
    _mbh->state = MB_TX_IDLE;
    _mbh->targetFrameIntv = frameIntv;
    _mbh->rspTimeout = rspTimeout;
    _mbh->txEnFn = txEnFn;
}

/**
  * @brief  modbus主机轮询处理
  *
  * @param  _mbh             : modbus主机管理实例
  *
  * @retval 返回轮询状态
  *    @arg MW_RET_OK        : 处理完成
  *    @arg MW_RET_ING       : 进行中
  *    @arg MW_RET_TIMEOUT   : 超时
  *    @arg MW_RET_LEN_ERR   : 数据长度错误
  *    @arg MW_RET_CHECK_ERR : 数据校验错误
  *    @arg MW_RET_FRAME_ERR : 数据格式错误
  */
uint8_t mb_host_poll(mb_host_t *_mbh)
{
    uint32_t tickTmp;
    uint16_t checkVal, startIndex, stopIndex;
    uint8_t ret = MW_RET_ING; /* 进行中 */
    
    switch(_mbh->state)
    {
        case MB_TX_IDLE: break;
        case MB_TX_BUSY: break;
        case MB_RX_IDLE:
            tickTmp = _mbh->rspTimeoutCnt;
            /* 应答超时判断 */
            if(tickTmp && (mw_tick_get() - tickTmp) > _mbh->rspTimeout)
            {
                _mbh->rspTimeoutCnt = 0;
                ret = MW_RET_TIMEOUT; /* 应答超时 */
                goto rspErr;
            }
            tickTmp = _mbh->curFrameIntv;
            /* 帧间隔超时判断 */
            if(tickTmp && (mw_tick_get() - tickTmp) > _mbh->targetFrameIntv)
            {
                _mbh->curFrameIntv = 0;
                _mbh->state = MB_RX_BUSY; /* 切换状态 */
#if _MBH_DEBUG
                MW_PRINT_FUNC("modbus host[%d] recv raw data: ", ch);
                for(uint16_t i = 0; i < _mbh->dataLen; i++)
                {
                    MW_PRINT_FUNC("%02X ", _mbh->dataBuf[i]);
                }
                MW_PRINT_FUNC("\r\n");
#endif
            }
            break;
        case MB_RX_BUSY:
            /* ASCII协议数据检查和转换 */
            if(MB_ASCII == _mbh->protocol)
            {
                /* 寻找起始符0x3A */
                for(startIndex = 0; startIndex < _mbh->dataLen; startIndex++)
                {
                    if(0x3A == _mbh->dataBuf[startIndex]) { break; }
                }
                /* 起始符索引检查 */
                if((_mbh->dataLen - startIndex - 1) < 10)
                {
                    ret = MW_RET_FRAME_ERR; /* 应答数据格式错误 */
                    goto rspErr;
                }
                /* 寻找结束符0x0D 0x0A */
                for(stopIndex = startIndex + 1; stopIndex < _mbh->dataLen; stopIndex += 2)
                {
                    if(0x0D == _mbh->dataBuf[stopIndex] && 0x0A == _mbh->dataBuf[stopIndex + 1]) { break; }
                }
                /* 结束符索引检查 */
                if(stopIndex >= _mbh->dataLen)
                {
                    ret = MW_RET_FRAME_ERR; /* 应答数据格式错误 */
                    goto rspErr;
                }
                _mbh->dataLen = stopIndex - startIndex - 1;
                /* ASCII转HEX */
                mb_ascii_to_hex(&_mbh->dataBuf[startIndex + 1], _mbh->dataLen, &_mbh->dataBuf[0]);
                _mbh->dataLen >>= 1;
#if _MBH_DEBUG
                MW_PRINT_FUNC("modbus host[%d] convert data: ", ch);
                for(uint16_t i = 0; i < _mbh->dataLen; i++)
                {
                    MW_PRINT_FUNC("%02X ", _mbh->dataBuf[i]);
                }
                MW_PRINT_FUNC("\r\n");
#endif
            }
            /* 应答的从机地址与请求时不同 */
            if(_mbh->dataBuf[MB_ADDR_OFF] != _mbh->slaveTmp)
            {
                ret = MW_RET_ING; /* 进行中 */
                goto rspErr;
            }
            /* 应答数据长度判断 */
            if(MB_PDU_SIZE_MIN > _mbh->dataLen)
            {
                ret = MW_RET_LEN_ERR; /* 应答数据长度错误 */
                goto rspErr;
            }
            /* ASCII协议 */
            if(MB_ASCII == _mbh->protocol)
            {
                /* LRC校验判断 */
                checkVal = mb_check_lrc(&_mbh->dataBuf[0], _mbh->dataLen - 1);
                if(checkVal != _mbh->dataBuf[_mbh->dataLen - 1])
                {
                    ret = MW_RET_CHECK_ERR; /* 应答数据校验错误 */
                    goto rspErr;
                }
            } /* RTU协议 */
            else
            {
                /* CRC校验判断 */
                checkVal = mb_check_crc16(&_mbh->dataBuf[0], _mbh->dataLen - 2);
                if(checkVal != ((_mbh->dataBuf[_mbh->dataLen - 2]) | _mbh->dataBuf[_mbh->dataLen - 1]  << 8))
                {
                    ret = MW_RET_CHECK_ERR; /* 应答数据校验错误 */
                    goto rspErr;
                }
            }
            /* 应答处理 */
            mb_host_rspProcess(_mbh);
            ret = MW_RET_OK; /* 应答处理完成 */
rspErr:
            _mbh->state = MB_TX_IDLE; /* 切换状态 */
            break;
        default: break;
    }
    
    return ret;
}

/**
  * @brief  主机发送请求
  *
  * @note   功能码 --- 扩展数据格式
  *         0x05   --- 一个线圈数据 1byte 0:OFF 1:ON 
  *         0x06   --- 一个寄存器数据 2byte
  *         0x0F   --- 多个线圈数据 1byte型指针
  *         0x10   --- 多个寄存器数据 2byte型指针
  *
  * @param  _mbh       : modbus主机管理实例
  *
  * @param  slaveAddr  : 从机地址
  *
  * @param  funCode    : 功能码(支持0x01,0x02,0x03,0x04,0x05,0x06,0x0F,0x10)
  *
  * @param  reg        : 起始地址
  *
  * @param  num        : 数量
  *
  * @param  extend     : 扩展数据(不同功能码对应格式不同)
  *
  * @retval 返回发送结果
  *    @arg MW_RET_OK  : 成功
  *    @arg MW_RET_ERR : 失败(不支持的功能码或数量超出合理范围)
  */
uint8_t mb_host_send_cmd(mb_host_t *_mbh, uint8_t slaveAddr, uint8_t funCode, uint16_t reg, uint16_t num, void *extend)
{
    /* 在TX_IDLE状态可以发送数据 */
    if(MB_TX_IDLE != _mbh->state) { return MW_RET_ERR; }
    /* 暂存参数 */
    _mbh->slaveTmp = slaveAddr;
    _mbh->numTmp = num;
    _mbh->regTmp = reg;
    /* 从机地址为0则为广播发送,非0则非广播发送 */
    if(slaveAddr) { _mbh->broadcast = 0; }
    else { _mbh->broadcast = 1; }
    
    switch(funCode)
    {
        case 0x01: /* 读线圈请求 */
        case 0x02: /* 读离散量输入请求 */
            if(MB_RD_COIL_NUM_MAX >= num && num)
            {
                mb_host_cmd_01_04h(_mbh, slaveAddr, funCode, reg, num);
            }
            break;
        case 0x03: /* 读保持寄存器请求 */
        case 0x04: /* 读输入寄存器请求 */
            if(MB_RD_REG_NUM_MAX >= num && num)
            {
                mb_host_cmd_01_04h(_mbh, slaveAddr, funCode, reg, num);
            }
            break;
        case 0x05: /* 写单个线圈请求 */
            mb_host_cmd_05h(_mbh, slaveAddr, reg, *(uint8_t *)extend);
            break;
        case 0x06: /* 写单个保持寄存器请求 */
            mb_host_cmd_06h(_mbh, slaveAddr, reg, *(uint16_t *)extend);
            break;
        case 0x0F: /* 写多个线圈请求 */
            if(MB_WR_COIL_NUM_MAX >= num && num)
            {
                mb_host_cmd_0fh(_mbh, slaveAddr, reg, num, (uint8_t *)extend);
            }
            break;
        case 0x10: /* 写多个寄存器请求 */
            if(MB_WR_REG_NUM_MAX >= num && num)
            {
                mb_host_cmd_10h(_mbh, slaveAddr, reg, num, (uint16_t *)extend);
            }
            break;
        /*
        用户在这里写其他功能码的发送处理
        */
        default: /* 不支持的请求 */
            break;
    }
    
    return (_mbh->state == MB_TX_BUSY) ? MW_RET_OK : MW_RET_ERR;
}

/**
  * @brief  写入数据到接收缓冲区
  *
  * @note   一般加入到(串口)接收中断
  *
  * @param  _mbh : modbus主机管理实例
  *
  * @param  pBuf : 数据缓存区
  *
  * @param  len  : 写入长度byte
  *
  * @retval 返回实际写入的数据数量
  */
uint16_t mb_host_write_rx_buf(mb_host_t *_mbh, void *pBuf, uint16_t len)
{
    uint16_t unusedLen;

    /* 在RX_IDLE状态下可以接收数据 */
    if(MB_RX_IDLE == _mbh->state)
    {
        /* 计算可写数量 */
        unusedLen = _mbh->dataBufLen - _mbh->dataLen;
        len = (len > unusedLen) ? unusedLen : len;
        
        if(0 < len)
        {
            /* 接续copy数据并记录数量 */
            memcpy((void *)&_mbh->dataBuf[_mbh->dataLen], pBuf, len);
            _mbh->dataLen += len;
            _mbh->curFrameIntv = mw_tick_get(); /* 开启帧间隔超时计数 */
            _mbh->rspTimeoutCnt = 0; /* 关闭应答超时计数 */
            
            return len;
        }
    }
    
    return 0;
}

/**
  * @brief  读出数据从发送缓冲区
  *
  * @note   一般加入到(串口)发送中断
  *
  * @param  _mbh : modbus主机管理实例
  *
  * @param  pBuf : 数据存储区
  *
  * @param  len  : 读出长度byte
  *
  * @retval 返回实际读出的数据数量
  */
uint16_t mb_host_read_tx_buf(mb_host_t *_mbh, void *pBuf, uint16_t len)
{
    uint16_t unreadLen;
    
    /* 在TX_BUSY状态下可以发送数据 */
    if(MB_TX_BUSY == _mbh->state)
    {
        /* 计算可读数量 */
        unreadLen = _mbh->dataLen - _mbh->readLen;
        len = (len > unreadLen) ? unreadLen : len;
        
        if(0 < len)
        {
            /* copy数据至外部存储区并记录数量 */
            memcpy(pBuf, (void *)&_mbh->dataBuf[_mbh->readLen], len);
            _mbh->readLen += len;
            /* 数据被全部copy出去 */
            if(_mbh->dataLen == _mbh->readLen)
            {
                /* 清零相关计数 */
                _mbh->dataLen = 0;
                _mbh->readLen = 0;
                /* 以广播发出 */
                if(_mbh->broadcast)
                {
                    _mbh->state = MB_TX_IDLE; /* 切换状态 */
                } /* 非广播发出 */
                else
                {
                    _mbh->rspTimeoutCnt = mw_tick_get(); /* 开启应答超时计数 */
                    _mbh->state = MB_RX_IDLE; /* 切换状态 */
                }
            }
            
            return len;
        }
    }
    _mbh->txEnFn(0); /* 关闭发送 */
    
    return 0;
}

/**
  * @brief  设置协议类型
  *
  * @note   初始化后仍可修改,需保持通信协议与设定协议一致,不然无法解析成功
  *
  * @param  _mbh     : modbus主机管理实例
  *
  * @param  protocol : 协议类型 @ref modbus_common_protocol
  *   @arg  MB_RTU   : RTU协议
  *   @arg  MB_ASCII : ASCII协议
  *
  * @retval None
  */
void mb_host_set_protocol(mb_host_t *_mbh, uint8_t protocol)
{
    MW_ASSERT_PARAM(IS_VALID_MB_PROTOCOL(protocol));
    
    _mbh->protocol = protocol;
}

/**
  * @brief  获取协议类型
  *
  * @param  _mbh     : modbus主机管理实例
  *
  * @retval 返回协议类型 @ref modbus_common_protocol
  *    @arg MB_RTU   : RTU协议
  *    @arg MB_ASCII : ASCII协议
  */
uint8_t mb_host_get_protocol(mb_host_t *_mbh)
{
    return _mbh->protocol;
}

/**
  * @brief  设置帧间隔超时时间
  *
  * @param  _mbh      : modbus主机管理实例
  *
  * @param  frameIntv : 帧间隔超时时间ms
  *
  * @retval None
  */
void mb_host_set_frame_intv(mb_host_t *_mbh, uint32_t frameIntv)
{
    _mbh->targetFrameIntv = frameIntv;
}

/**
  * @brief  获取帧间隔超时时间
  *
  * @param  _mbh : modbus主机管理实例
  *
  * @retval 返回帧间隔超时时间ms
  */
uint32_t mb_host_get_frame_intv(mb_host_t *_mbh)
{
    return _mbh->targetFrameIntv;
}

/**
  * @brief  设置应答超时时间
  *
  * @param  _mbh    : modbus主机管理实例
  *
  * @param  timeout : 应答超时时间ms
  *
  * @retval None
  */
void mb_host_set_rsp_timeout(mb_host_t *_mbh, uint32_t timeout)
{
    _mbh->rspTimeout = timeout;
}

/**
  * @brief  获取应答超时时间
  *
  * @param  _mbh : modbus主机管理实例
  *
  * @retval 返回应答超时时间ms
  */
uint32_t mb_host_get_rsp_timeout(mb_host_t *_mbh)
{
    return _mbh->rspTimeout;
}

/**
  * @brief  读线圈应答处理(0x01)
  *
  * @note   从机地址 | 功能码 | 字节数n | 线圈状态 | CRC16
  *             1    |   1    |    1    |    n     |   2   (占用字节)
  *
  * @param  _mbh  : modbus主机管理实例
  *
  * @param  reg   : 起始地址
  *
  * @param  num   : 线圈数量 1线圈=1bit
  *
  * @param  pData : 线圈状态
  *
  * @retval None
  */
__weak void mb_host_rsp_01h(mb_host_t *_mbh, uint16_t reg, uint16_t num, uint8_t *pData)
{
#if _MBH_DEBUG
    uint8_t bytes = (num + 7) >> 3;
    
    MW_PRINT_FUNC("modbus host[%d] rsp01h> coilAddr:0x%X, coilNum:%d, data:", ch, reg, num);
    for(uint8_t i = 0; i < bytes; i++)
    {
        MW_PRINT_FUNC("%X ", pData[i]);
    }
    MW_PRINT_FUNC("\r\n");
#endif
}

/**
  * @brief  读离散量输入应答处理(0x02)
  *
  * @note   从机地址 | 功能码 | 字节数n | 离散量状态 | CRC16
  *             1    |   1    |    1    |     n      |   2   (占用字节)
  *
  * @param  _mbh  : modbus主机管理实例
  *
  * @param  reg   : 起始地址
  *
  * @param  num   : 离散量数量 1离散量=1bit
  *
  * @param  pData : 离散量状态
  *
  * @retval None
  */
__weak void mb_host_rsp_02h(mb_host_t *_mbh, uint16_t reg, uint16_t num, uint8_t *pData)
{
#if _MBH_DEBUG
    uint8_t bytes = (num + 7) >> 3;
    
    MW_PRINT_FUNC("modbus host[%d] rsp02h> coilAddr:0x%X, coilNum:%d, data:", ch, reg, num);
    for(uint8_t i = 0; i < bytes; i++)
    {
        MW_PRINT_FUNC("%X ", pData[i]);
    }
    MW_PRINT_FUNC("\r\n");
#endif
}

/**
  * @brief  读保持寄存器应答处理(0x03)
  *
  * @note   从机地址 | 功能码 | 字节数n | 保持寄存器值 | CRC16
  *             1    |   1    |    1    |       n      |   2   (占用字节)
  *
  * @param  _mbh  : modbus主机管理实例
  *
  * @param  reg   : 起始地址
  *
  * @param  num   : 寄存器数量 1寄存器=16bit
  *
  * @param  pData : 保持寄存器值
  *
  * @retval None
  */
__weak void mb_host_rsp_03h(mb_host_t *_mbh, uint16_t reg, uint16_t num, uint8_t *pData)
{
#if _MBH_DEBUG
    uint8_t bytes = num << 1;
    
    MW_PRINT_FUNC("modbus host[%d] rsp03h> regAddr:0x%X, regNum:%d, data:", ch, reg, num);
    for(uint8_t i = 0; i < bytes; i++)
    {
        MW_PRINT_FUNC("%X ", pData[i]);
    }
    MW_PRINT_FUNC("\r\n");
#endif
}

/**
  * @brief  读输入寄存器应答处理(0x04)
  *
  * @note   从机地址 | 功能码 | 字节数n | 输入寄存器值 | CRC16
  *             1    |   1    |    1    |       n      |   2   (占用字节)
  *
  * @param  _mbh  : modbus主机管理实例
  *
  * @param  reg   : 起始地址
  *
  * @param  num   : 输入寄存器数量 1输入寄存器=16bit
  *
  * @param  pData : 输入寄存器值
  *
  * @retval None
  */
__weak void mb_host_rsp_04h(mb_host_t *_mbh, uint16_t reg, uint16_t num, uint8_t *pData)
{
#if _MBH_DEBUG
    MW_PRINT_FUNC("modbus host[%d] rsp04h> regAddr:0x%X, regNum:%d, data:", ch, reg, num);
    for(uint8_t i = 0; i < num; i++)
    {
        MW_PRINT_FUNC("%X ", (pData[i << 1] << 8) | pData[(i << 1) + 1]);
    }
    MW_PRINT_FUNC("\r\n");
#endif
}

/**
  * @brief  功能码0x05 0x06 0x0F 0x10应答处理
  *
  * @note   从机地址 | 功能码 | 起始地址 | 写入值/数量 | CRC16
  *             1    |   1    |    2     |      2      |   2   (占用字节)
  *
  * @param  _mbh : modbus主机管理实例
  *
  * @retval None
  */
__weak void mb_host_rsp_05_10h(mb_host_t *_mbh)
{
#if _MBH_DEBUG
    MW_PRINT_FUNC("modbus host[%d] rsp05h_06h_0fh_10h> success\r\n", ch);
#endif
}

/**
  * @brief  错误码应答处理(功能码+0x80)
  *
  * @note   从机地址 | 错误功能码 | 错误代码 | CRC16
  *             1    |      1     |    1     |   2   (占用字节)
  *
  * @param  _mbh  : modbus主机管理实例
  *
  * @param  pData : 错误数据(错误功能码和错误代码)
  *
  * @retval None
  */
__weak void mb_host_rsp_err(mb_host_t *_mbh, uint8_t *pData)
{
/*
    部分错误代码释义:
    0x01 - 非法功能     : 功能码无效
    0x02 - 非法数据地址 : 地址无效或地址和数量的组合无效
    0x03 - 非法数据值   : 数据值无效
    0x04 - 从机故障     : 当从机试图执行请求的操作时,发生了不可恢复的错误
*/
#if _MBH_DEBUG
    MW_PRINT_FUNC("modbus host[%d] rspErr> errFunCode:0x%X, errCode:0x%X\r\n", ch, pData[0], pData[1]);
#endif
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
